package checkers3d.logic;

import checkers3d.presentation.GUIButton;
import checkers3d.presentation.GUIContainer;
import checkers3d.presentation.GUILabel;
import checkers3d.presentation.IInputObserver;
import checkers3d.presentation.RenderResourceBitmap;
import checkers3d.presentation.UtilGUI;
import java.awt.Point;
import java.awt.Color;
import java.util.LinkedList;

/**
 * Class that allows two PlayerControllers to alternate between placing pieces
 * on the 2D boards of a PlayingSurface3D. Creates a Game object from
 * PlayingSurface3D once setup is completed.
 *
 * @author Ruben Acuna
 * @see GameBase
 */
public class GameSetupUser extends GameBase
{
    /**
     * The current piece selection. null is no piece is selected.
     */
    private IBoardObject currentPiece = null;

    /**
     * List of objects representing possible pieces the home player may place.
     */
    private LinkedList<IBoardObject> availableHomePieces = new LinkedList<IBoardObject>();

    /**
     * List of objects representing possible pieces the visitor player may place.
     */
    private LinkedList<IBoardObject> availableVisitorPieces = new LinkedList<IBoardObject>();

    /**
     * The position of the home player's piece tray.
     */
    private Point POSITION_HOME;

    /**
     * The position of the visitor player's piece tray.
     */
    private Point POSITION_VISITOR;

    /**
     * The position of the done button.
     */
    private Point POSITION_DONE;

    /**
     * The button object for signaling the completion of game setup.
     */
    private  GUIButton buttonDone;

    /**
     * Creates a window that displays a PlayingSurface3D. Adds 'trays' which are
     * a set of buttons indicating which pieces a player may place. The home
     * player will place first. Once all pieces have been added, a Game can be
     * created and played.
     * 
     * @param home The home Player.
     * @param visitor The visiting Player.
     * @param boardSetup A PlayingSurface3D that this object setups up for play.
     * @param extended Indicates rule game selection (piece types).
     * @param size The size of the window.
     * @see GameBase
     */
    public GameSetupUser(Player home, Player visitor, PlayingSurface3D boardSetup, boolean extended, Point size)
    {
        super(home, visitor, visitor, boardSetup, size);

        //create the done button - location will be set by computeLocations.
        buttonDone = new GUIButton("button-done.png", null, new ObserverDone(home, visitor, playingSurface3D, this));
        add(buttonDone);

        availableHomePieces.add(new BoardObjectChecker(home, home.getCheckerRR()));

        availableVisitorPieces.add(new BoardObjectChecker(visitor, visitor.getCheckerRR()));

        //add kingifys for both players
        for (int x = 0; x < boardSizeX; x = x + 2)
            for (int z = 0; z < boardSizeZ; z++)
            {
                playingSurface3D.getPositionObjects(new Point3D(x, 0, z)).add(new BoardObjectKingify(home));
                playingSurface3D.getPositionObjects(new Point3D(x, boardSizeY - 1, z)).add(new BoardObjectKingify(visitor));
            }
        
        if(extended)
        {
            availableHomePieces.add(new BoardObjectKing(home, home.getKingRR()));
            availableHomePieces.add(new BoardObjectMine(home, home.getMineRR()));
            availableHomePieces.add(new BoardObjectSafe(home, home.getSafeRR()));
            availableHomePieces.add(new BoardObjectWall(home, home.getWallRR()));

            availableVisitorPieces.add(new BoardObjectKing(visitor, visitor.getKingRR()));
            availableVisitorPieces.add(new BoardObjectMine(visitor, visitor.getMineRR()));
            availableVisitorPieces.add(new BoardObjectSafe(visitor, visitor.getSafeRR()));
            availableVisitorPieces.add(new BoardObjectWall(visitor, visitor.getWallRR()));
        }

        computeLocations();
        buttonDone.setDrawPosition(POSITION_DONE);
        
        buildPieceTrays();
    }

    /**
     * Calculates the position on screen of the player piece trays for any
     * board configuration.
     */
    public void computeLocations()
    {
        if(playingSurface3D.getSizeZ() == 1)
        {
            POSITION_HOME = new Point (10, boardLocations[0].y - TILE_SIZE);
            POSITION_VISITOR = new Point (getSize().x / 2 + TILE_SIZE, boardLocations[0].y - TILE_SIZE);
            POSITION_DONE = new Point((getSize().x - buttonDone.getRenderResource().getWidth()) / 2,
                                       getSize().y - buttonDone.getRenderResource().getHeight());
        }
        else if(playingSurface3D.getSizeZ() == 2)
        {
            POSITION_HOME = new Point (10, 50);
            POSITION_VISITOR = new Point (boardLocations[0].x + playingSurface3D.getSizeX() * TILE_SIZE + 10, getSize().y - 50 - TILE_SIZE);
            POSITION_DONE = new Point(getSize().x - buttonDone.getRenderResource().getWidth(),
                                      getSize().y - buttonDone.getRenderResource().getHeight());
        }
        else if(playingSurface3D.getSizeZ() == 3)
        {
            POSITION_HOME = new Point (10, 50);
            POSITION_VISITOR = new Point (boardLocations[0].x + (playingSurface3D.getSizeX() + 1) * TILE_SIZE, getSize().y - 50 - TILE_SIZE);
            POSITION_DONE = new Point(getSize().x - TILE_SIZE * 4,
                                      getSize().y - TILE_SIZE * 4);
        }
        else if(playingSurface3D.getSizeZ() == 4)
        {
            POSITION_HOME = new Point (10, (getSize().y - TILE_SIZE) / 2);
            POSITION_VISITOR = new Point (getSize().x / 2 + TILE_SIZE + 10, (getSize().y - TILE_SIZE) / 2);
            POSITION_DONE = new Point((getSize().x - buttonDone.getRenderResource().getWidth()) / 2,
                                       getSize().y - buttonDone.getRenderResource().getHeight());
        }
        else
            throw new UnsupportedOperationException("K board support is not implemented yet.");
    }

    /**
     * Creates buttons for each of each player's placeable pieces.
     */
    private void buildPieceTrays()
    {
        int x = 0;

        //add buttons for everything in home's available pieces list
        for(IBoardObject object : availableHomePieces)
        {
            Point buttonLocation = new Point (POSITION_HOME.x + x, POSITION_HOME.y);

            GUIButton objectButton = new GUIButton((RenderResourceBitmap)object.getRenderResource(),
                                                   buttonLocation, new ObserverPiece(object.clone(), this));

            add(objectButton);

            x = x + object.getRenderResource().getWidth() + 20;
        }

        x = 0;

        //add buttons for everything in visitor's available pieces list
        for(IBoardObject object : availableVisitorPieces)
        {
            Point buttonLocation = new Point (POSITION_VISITOR.x + x, POSITION_VISITOR.y);

            GUIButton objectButton = new GUIButton((RenderResourceBitmap)object.getRenderResource(),
                                                   buttonLocation, new ObserverPiece(object.clone(), this));

            add(objectButton);

            x = x + object.getRenderResource().getWidth() + 20;
        }
    }

    /**
     * Given a IBoardObject, returns the number of objects within the current
     * PlayingSurface3D which are of the same class type and have the same owner.
     *
     * @param countee Model object which count 'instances' of.
     * @return Returns the number of IBoardObject 'instances'
     */
    private int countInstances(IBoardObject countee)
    {
        int count = 0;
        for(int x = 0; x < boardSizeX; x++)
            for(int y = 0; y < boardSizeY; y++)
                for(int z = 0; z < boardSizeZ; z++)
                {
                    Point3D target = new Point3D(x ,y, z);

                    //instanceof only works when the RHS is an object class name
                    //so we have to do something much cruder:
                    for(IBoardObject object : playingSurface3D.getPositionObjects(target))
                        if(object.getClass().getName().equals(countee.getClass().getName())
                        && object.getOwner() == countee.getOwner())
                            count++;
                }
        
        return count;
    }

    /**
     * Checks if the click happens on a tile that the current piece can be placed.
     * If so, it places the piece and cycles to the next player.
     *
     * @param position Location of click within button.
     */
    @Override
    public void onClick(Point position)
    {
        super.onClick(position);

        if(getCurrentPlayerController() instanceof IInputObserver)
            ((IInputObserver)getCurrentPlayerController()).onClick(position);

        Point3D point3D = getCurrentPlayerController().popLastMove();

        if(point3D != null && currentPiece != null)
            if(playingSurface3D.getOverlay(point3D) == PlayingSurfacePositionOverlay.SETUPABLE)
            {
                playingSurface3D.addObject(currentPiece.clone(), point3D);

                nextPlayerController();
            }
    }

    /**
     * Return boolean indicating if all pieces have been placed.
     *
     * @return A boolean indicating pieces are placed.
     */
    public boolean isSetupComplete()
    {
        return (getHomeRemaining().length() == 0 && getVisitorRemaining().length() == 0);
    }

    /**
     * Checks to see which tiles the current piece can be placed on and
     * highlights those in the PlayingSurface3D's overlays.
     */
    private void buildValidSetupPositions()
    {
        clearOverlays();

        //if the visitor is placing, flip the board to preform logic.
        if(playerVisitor == getCurrentPlayerController())
            playingSurface3D.flip();

        //probe all board locations for valid setup locations.
        for (int x = 0; x < boardSizeX; x = x + 2)
            for (int y = 0; y < boardSizeY; y++)
                for (int z = 0; z < boardSizeZ; z++)
                {
                    Point3D target = new Point3D(x, y, z);

                    if (y % 2 == 0)
                        target.x = target.x + 1;

                    if(currentPiece != null && currentPiece.isValidSetupPosition(playingSurface3D, target))
                        playingSurface3D.getPosition(target).setOverlay(PlayingSurfacePositionOverlay.SETUPABLE);
                }

        //if the visitor was placing, the board was flipped, so reflip.
        if(playerVisitor == getCurrentPlayerController())
            playingSurface3D.flip();
    }

    /**
     * Sets the current piece that is being placed.
     *
     * @param piece A IBoardObject piece.
     */
    public void setCurrentPiece(IBoardObject piece)
    {
        currentPiece = piece;
    }

    /**
     * Returns a descriptions of the pieces home still needs to setup.
     *
     * @return A string description of missing home pieces.
     */
    public String getHomeRemaining()
    {
        return listRemainingPieces(availableHomePieces);
    }

    /**
     * Returns a descriptions of the pieces visitor still needs to setup.
     * @return A string description of missing visitor pieces.
     */
    public String getVisitorRemaining()
    {
        return listRemainingPieces(availableVisitorPieces);
    }

    /**
     * Given a list of 'model' pieces, checks to see if each piece in that list
     * has a number of instances in the PlayingSurface3D equal it's setup count.
     * If some are missing, it generates a non-empty string description.
     *
     * @param pieces List of model pieces.
     * @return A string description of missing pieces.
     */
    private String listRemainingPieces(LinkedList<IBoardObject> pieces)
    {
        String missing = new String();
        
        for(IBoardObject piece : pieces)
        {
            int instancesCount = countInstances(piece);
            int setupCount = piece.getSetupCount(boardSizeX, boardSizeY, boardSizeZ);

            if(instancesCount < setupCount)
            {
                missing = missing + (setupCount - instancesCount) + " " + piece.getClass().getSimpleName().substring(11) + "  ";
            }
        }

        return missing;
    }

    /**
     * Key press observer that does nothing.
     *
     * @param key Key that was pressed.
     */
    @Override
    public void onKeyPress(char key)
    {
        //do nothing.
    }

    /**
     * Returns a string representation of this object containing it's class name.
     *
     * @return String representation.
     */
    @Override
    public String toString()
    {
        return getClass().getName();
    }

    /**
     * A generic observer for the buttons that select pieces for placement.
     */
    private class ObserverPiece implements IInputObserver
    {
        /**
         * The piece attacted to this observer.
         */
        IBoardObject piece;

        /**
         * The GameSetupUser object this observer's button exists in.
         */
        GameSetupUser parent;

        /**
         * Constructor that setups up this observer's piece and parent objects.
         *
         * @param piece The piece attacted to this observer.
         * @param parent The GameSetupUser object this observer's button exists in.
         */
        public ObserverPiece(IBoardObject piece, GameSetupUser parent)
        {
            this.piece = piece;
            this.parent = parent;
        }

        /**
         * If the current player controls this placement of this piece and they
         * haven't place too many pieces already, builds the valid setup positions
         * and sets the current piece. Otherwise, sets the current piece to be
         * null and resets the overlays.
         *
         * @param position Location of click within button.
         */
        public void onClick(Point position)
        {
            if(parent.countInstances(piece) < piece.getSetupCount(parent.boardSizeX, 
                                                                  parent.boardSizeY,
                                                                  parent.boardSizeZ)
               && piece.getOwner() == parent.getCurrentPlayer())
            {
                parent.setCurrentPiece(piece);
                parent.buildValidSetupPositions();
            }
            else
            {
                parent.setCurrentPiece(null);
                parent.clearOverlays();
            }
        }

        /**
         * Key press observer that does nothing.
         *
         * @param key Key that was pressed.
         */
        public void onKeyPress(char key)
        {
            //do nothing.
        }
    }

    /**
     * An observer that checks if a player has completed setup if so, creates
     * Game object for them to player. Otherwise, display a error message listing
     * pieces that need to be setup.
     */
    private class ObserverDone implements IInputObserver
    {
        /**
         * The home Player.
         */
        Player home;

        /**
         * The visitor Player.
         */
        Player visitor;

        /**
         * The current PlayingSurface3D that was setup.
         */
        PlayingSurface3D playingSurface3D;

        /**
         * The GameSetupUser object this observer's button exists in.
         */
        GameSetupUser parent;

        /**
         *
         *
         * @param home The home Player.
         * @param visitor The visitor Player.
         * @param playingSurface3D The PlayingSurface3D that was setup.
         * @param parent The GameSetupUser object this observer's button exists in.
         */
        public ObserverDone(Player home, Player visitor,
                            PlayingSurface3D playingSurface3D, GameSetupUser parent)
        {
            this.home = home;
            this.visitor = visitor;
            this.playingSurface3D = playingSurface3D;
            this.parent = parent;
        }

        /**
         * Checks if setup is complete. If so, removes the piece placement
         * window, the game setup window and adds a new Game object to render
         * stack.
         *
         * @param position Location of click within button.
         */
        public void onClick(Point position)
        {
            if(parent.isSetupComplete())
            {
                //clear any overlays remaining from piece placement.
                clearOverlays();

                //remove GameSetupUser
                GameMaster.getInstance().removeTopGUIContainer();

                //add the game
                GameMaster.getInstance().addGUIContainer(new Game(home, visitor, playingSurface3D, parent.getSize()));
            }
            //push a GUIContainer on the render stack listing unplaced pieces.
            else
            {
                GUIContainer warning = new GUIContainer(parent.getSize()) { 
                    @Override
                    public void onClick(Point position)
                    {
                        GameMaster.getInstance().removeTopGUIContainer();
                    }
                };

                warning.add(new GUILabel("The home player needs to place: " + parent.getHomeRemaining(), Color.WHITE, new Point()));
                warning.add(new GUILabel("The visiting player needs to place: " + parent.getVisitorRemaining(), Color.WHITE, new Point()));
                UtilGUI.computeLayoutCentered(warning.getSize(), getSize().x / 2, warning.getComponentsList());

                GameMaster.getInstance().addGUIContainer(warning);
            }
        }

        /**
         * Key press observer that does nothing.
         *
         * @param key Key that was pressed.
         */
        public void onKeyPress(char key)
        {
            //do nothing.
        }
    }
}